package middleware;

import java.io.*;
import java.net.Socket;
import java.net.UnknownHostException;

import java.util.Properties;
import java.util.ArrayList;
import java.util.Random;
import java.util.StringTokenizer;
import java.util.Calendar;

import logging.Logging;


// basic client class (entry point on client machines)
public class Client {

	//client stuff
	private Properties properties = new Properties();
	private int timeToNextQuery;
	private long runningTime;
	private ArrayList<Integer> updateFiles;

	/**
	 * @uml.property  name="sg"
	 * @uml.associationEnd  
	 */
	private StatementGeneration sg;
	private int nextMiddleware;

	//middleware stuff
	private ArrayList<Socket> mwSockets;
	private ArrayList<String> mwHosts;
	private ArrayList<Integer> mwPorts;
	private ArrayList<PrintStream> outputStreams;
	private ArrayList<DataInputStream> inputStreams;
	private Random randomizer;
	static private Integer updateMiddleware = 0;

	/**
	 * @uml.property  name="tester"
	 * @uml.associationEnd  
	 */
	private Tester tester;
	private boolean testQueries;



	/**
	 * Creater class. Initializes data from config.properties and creates input/output streams.
	 */
	Client () {
		FileInputStream iStream;
		sg = new StatementGeneration();
		try {
			iStream  = new FileInputStream("config/config.properties");
			properties.load(iStream);
			iStream.close();
		} catch (FileNotFoundException e) {
			System.err.println("File not found: " + e.getMessage());
		} catch (IOException e) {
			System.err.println("IO error: " + e.getMessage());
		}

		timeToNextQuery = new Integer(properties.getProperty("timeToNextQuery").trim());

		runningTime = new Long(properties.getProperty("runningTime").trim());

		// set all update files
		String filesString = properties.getProperty("updateFiles");
		updateFiles = new ArrayList<Integer>();
		StringTokenizer st = new StringTokenizer(filesString, ",");
		while (st.hasMoreTokens()) {
			updateFiles.add(new Integer(st.nextToken().trim()));
		}

		//set all the MW Hosts
		String hostsString = properties.getProperty("mwHost");
		mwHosts = new ArrayList<String>();
		st = new StringTokenizer(hostsString, ",");
		while (st.hasMoreTokens()) {
			mwHosts.add(st.nextToken().trim());
		}

		// set all Ports
		String portsString = properties.getProperty("mwPortNr");
		mwPorts = new ArrayList<Integer>();
		st = new StringTokenizer(portsString, ",");
		while (st.hasMoreTokens()) {
			mwPorts.add(new Integer(st.nextToken().trim()));
		}


		Random randomGenerator = new Random();
		nextMiddleware = randomGenerator.nextInt(mwHosts.size());
		tester = new Tester();

		String testString = properties.getProperty("testQueries");
		testQueries = new Boolean(testString.trim());

		randomizer = new Random();
		tester = new Tester();
	}

	/**
	 * Creates connection to middleware servers (one each) and stores into mySockets.
	 */
	public void makeConnections () {

		mwSockets = new ArrayList<Socket>();
		outputStreams = new ArrayList<PrintStream>();
		inputStreams = new ArrayList<DataInputStream>();

		for (int i = 0; i < mwHosts.size(); i++) {
			Socket s = null;
			try {
				s = new Socket(mwHosts.get(i), mwPorts.get(i));
				// TODO: maybe do a connect
			} catch (UnknownHostException e) {
				System.err.println(e.getMessage());
				//e.printStackTrace();
			} catch (IOException e) {
				System.err.println(e.getMessage());
				//e.printStackTrace();
			}
			mwSockets.add(s);

			// creating the streams
			try {
				outputStreams.add(new PrintStream(s.getOutputStream()));
				inputStreams.add(new DataInputStream(s.getInputStream()));
			} catch (IOException e) {
				System.err.println("Got problems with initializing the streams: " + e.getMessage());
			}
		}

	}

	/**
	 * Terminates all connections.
	 */
	public void closeConnections() {

		for (int i = 0; i < outputStreams.size(); i++) {

			try {
				outputStreams.get(i).close();
				inputStreams.get(i).close();
			} catch (IOException e) {
				System.err.println("Error with closing the connections: " + e.getMessage());
			}

		}

	}

	/**
	 * Sends a statement to the middleware server. Update Server in case of refresh function.
	 * For queries, the middleware server is chosen in a round robin manner.
	 * @param inString is de query to be sent.
	 * @param type explains the type of the statement (0=update, 1=query, 2=preparedStatementBase).
	 * @return Result string.
	 */
	@SuppressWarnings("deprecation")
	public String sendStatement(String inString, int type) {

		//Round robin
		nextMiddleware = (nextMiddleware + 1) % mwSockets.size();

		try {

			switch (type) {
			case 0:
				// send to dedicated (= first) middleware server
				outputStreams.get(updateMiddleware).println(inString);
				return inputStreams.get(updateMiddleware).readLine();

			case 1:
				// round robin
				outputStreams.get(nextMiddleware).println(inString);
				return inputStreams.get(nextMiddleware).readLine();

			case 2:
				for (int i = 0; i < mwSockets.size(); i++) {
					outputStreams.get(i).println(inString);
					inputStreams.get(i).readLine();
				}
				return null;

			default:
				return null;
			}
		}
		catch (IOException e) {
			System.out.println(e);
		}
		return null;
	}


	/**
	 * sends the prepared statement form of the queries
	 */
	public void sendBaseQuery() {
		String q1 = sg.getPreparedQuery(1);
		ArrayList<String> rf1 = sg.getPreparedRF(1);
		ArrayList<String> rf2 = sg.getPreparedRF(2);

			this.sendStatement(q1, 2);
			this.sendStatement(rf1.get(0), 2);
			this.sendStatement(rf1.get(1), 2);
			this.sendStatement(rf2.get(0), 2);
			this.sendStatement(rf2.get(1), 2);

	}

	/**
	 * Main loop of the client, executing all updates and queries as requested
	 * for the specified amount of time (see config.properties).
	 */
	public void start() {

		Logging.openClientWriter();

		this.makeConnections();


		int numberOfUpdateFiles = updateFiles.size();

		this.sendBaseQuery();

		long endTime = runningTime * (60 * 1000) + Calendar.getInstance().getTimeInMillis();
		boolean quit = false;

		String updateResult = null, sendString = null;
		ArrayList<String> updateSet = new ArrayList<String>();


		while (!quit) {

			int fileNr = randomizer.nextInt(updateFiles.size());

			// RF 1
			updateSet = sg.getUpdate(1, updateFiles.get(fileNr));

			for (int u = 0; u < updateSet.size() && !quit; u++) {

				try {
					Thread.sleep(timeToNextQuery);
				} catch (InterruptedException e) {}

				sendString = updateSet.get(u);
				Logging.logEvent(1, "SENDING RF1", sendString);
				updateResult = this.sendStatement(sendString, 0);
				Logging.logEvent(4, "ANSRCV RF1", updateResult);

				quit = Calendar.getInstance().getTimeInMillis() > endTime;
			}


			// System.out.println(updateResult);

			// X times Q1
			for (int i = 0; i < 23 && !quit; i++) {
				if (quit = Calendar.getInstance().getTimeInMillis() > endTime) {
					break;
				}

				try {
					Thread.sleep(timeToNextQuery);
				} catch (InterruptedException e) {}

				int delta = sg.generateQ1Delta();

				sendString = "Q1 " + delta;
				Logging.logEvent(3, "SENDING Q1", sendString);
				String results = this.sendStatement(sendString, 1);

				if (testQueries) {
					if (tester.test(results)) {
						Logging.logEvent(6, "ANSRCV Q1", results);
					}
				} else {
					Logging.logEvent(6, "ANSRCV Q1", results);						
				}

				quit = Calendar.getInstance().getTimeInMillis() > endTime;

				//									Debug
				//									StringTokenizer st = new StringTokenizer(results, "|");
				//									while (st.hasMoreTokens()) 
				//									System.out.println(st.nextToken());
			}

			// RF 2
			updateSet = sg.getUpdate(2, updateFiles.get(fileNr));
			for (int u = 0; u < updateSet.size() && !quit; u++) {

				try {
					Thread.sleep(timeToNextQuery);
				} catch (InterruptedException e) {}

				sendString = updateSet.get(u);
				Logging.logEvent(2, "SENDING RF2", sendString);
				updateResult = this.sendStatement(sendString, 0);
				Logging.logEvent(5, "ANSRCV RF2", updateResult);

				quit = Calendar.getInstance().getTimeInMillis() > endTime;
			}

		}



		this.closeConnections();

		Logging.closeWriter();
	}

	public static void main(String[] args) {

		Client client = new Client();
		client.start();		

	}
}
